001 /*
002 * Copyright (c) 2005 Stephen J. McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.metro.tools;
020
021 import java.io.File;
022 import java.net.URI;
023 import java.net.URL;
024 import java.util.ArrayList;
025 import java.util.List;
026
027 import net.dpml.library.info.Scope;
028
029 import net.dpml.tools.tasks.GenericTask;
030
031 import net.dpml.state.Trigger;
032 import net.dpml.state.State;
033 import net.dpml.state.Operation;
034 import net.dpml.state.Interface;
035 import net.dpml.state.Transition;
036 import net.dpml.state.StateDecoder;
037 import net.dpml.state.DefaultState;
038
039 import org.apache.tools.ant.Project;
040 import org.apache.tools.ant.BuildException;
041 import org.apache.tools.ant.types.Path;
042 import org.apache.tools.ant.AntClassLoader;
043
044 /**
045 * Utility datatype supporting State instance construction.
046 *
047 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
048 * @version 1.1.1
049 */
050 public class StateDataType
051 {
052 private static final StateDecoder STATE_DECODER = new StateDecoder();
053
054 private final boolean m_root;
055 private final GenericTask m_task;
056
057 private String m_name;
058 private List m_states = new ArrayList();
059 private List m_operations = new ArrayList();
060 private List m_interfaces = new ArrayList();
061 private List m_transitions = new ArrayList();
062 private List m_triggers = new ArrayList();
063 private boolean m_terminal = false;
064
065 private URI m_uri;
066 private String m_classname;
067
068 StateDataType( GenericTask task )
069 {
070 this( task, false );
071 }
072
073 StateDataType( GenericTask task, boolean root )
074 {
075 m_root = root;
076 m_task = task;
077 }
078
079 /**
080 * Set the state name. Note that state names are only applicable to
081 * substates. A state name assigned to the root state will be ignored.
082 * @param name the name of the state
083 */
084 public void setName( final String name )
085 {
086 if( null == name )
087 {
088 throw new NullPointerException( "name" );
089 }
090 m_name = name;
091 }
092
093 /**
094 * Set a uri from which to resolve an encoded state graph. May only
095 * applied to a root state.
096 * @param uri a uri referencing a state graph artifact
097 */
098 public void setUri( final URI uri )
099 {
100 if( !m_root )
101 {
102 final String error =
103 "Illegal attempt to request import of a state graph within a nested state.";
104 throw new BuildException( error, m_task.getLocation() );
105 }
106 m_uri = uri;
107 }
108
109 /**
110 * Set a classname from which to resolve an embedded state graph. May only
111 * applied to a root state. May not be used in conjuction with other attributes or
112 * nested elements.
113 * @param classname the classname of a class containing a collocated xgraph resource
114 */
115 public void setClass( final String classname )
116 {
117 if( !m_root )
118 {
119 final String error =
120 "Illegal attempt to request import of a state graph within a nested state.";
121 throw new BuildException( error, m_task.getLocation() );
122 }
123 if( null != m_uri )
124 {
125 final String error =
126 "Illegal attempt to request import of a embedded state graph in conjuction with the uri attribute.";
127 throw new BuildException( error, m_task.getLocation() );
128 }
129 m_classname = classname;
130 }
131
132 /**
133 * Mark the state as a terminal state.
134 * @param flag true if this is a terminal state
135 */
136 public void setTerminal( final boolean flag )
137 {
138 if( null != m_uri )
139 {
140 final String error =
141 "Terminal attribute may not be used in conjuction with a uri import.";
142 throw new BuildException( error, m_task.getLocation() );
143 }
144 if( null != m_classname )
145 {
146 final String error =
147 "Terminal attribute may not be used in conjuction with the class attribute.";
148 throw new BuildException( error, m_task.getLocation() );
149 }
150 m_terminal = flag;
151 }
152
153 /**
154 * Add a substate within the state.
155 * @return the sub-state datatype
156 */
157 public StateDataType createState()
158 {
159 if( null != m_uri )
160 {
161 final String error =
162 "Substates may not be used in conjuction with a uri import.";
163 throw new BuildException( error, m_task.getLocation() );
164 }
165 if( null != m_classname )
166 {
167 final String error =
168 "Substates may not be used in conjuction with the class attribute.";
169 throw new BuildException( error, m_task.getLocation() );
170 }
171 final StateDataType state = new StateDataType( m_task );
172 m_states.add( state );
173 return state;
174 }
175
176 /**
177 * Add an operation within this state.
178 * @return the operation datatype
179 */
180 public OperationDataType createOperation()
181 {
182 if( null != m_uri )
183 {
184 final String error =
185 "Operations may not be used in conjuction with a uri import.";
186 throw new BuildException( error, m_task.getLocation() );
187 }
188 if( null != m_classname )
189 {
190 final String error =
191 "Operations may not be used in conjuction with the class attribute.";
192 throw new BuildException( error, m_task.getLocation() );
193 }
194 final OperationDataType operation = new OperationDataType();
195 m_operations.add( operation );
196 return operation;
197 }
198
199 /**
200 * Add an interface within this state.
201 * @return the interface datatype
202 */
203 public InterfaceDataType createInterface()
204 {
205 if( null != m_uri )
206 {
207 final String error =
208 "Interfaces may not be used in conjuction with a uri import.";
209 throw new BuildException( error, m_task.getLocation() );
210 }
211 if( null != m_classname )
212 {
213 final String error =
214 "Interfaces may not be used in conjuction with the class attribute.";
215 throw new BuildException( error, m_task.getLocation() );
216 }
217 final InterfaceDataType data = new InterfaceDataType();
218 m_interfaces.add( data );
219 return data;
220 }
221
222 /**
223 * Add an transition within this state.
224 * @return the operation datatype
225 */
226 public TransitionDataType createTransition()
227 {
228 if( null != m_uri )
229 {
230 final String error =
231 "Transitions may not be used in conjuction with a uri import.";
232 throw new BuildException( error, m_task.getLocation() );
233 }
234 if( null != m_classname )
235 {
236 final String error =
237 "Transitions may not be used in conjuction with the class attribute.";
238 throw new BuildException( error, m_task.getLocation() );
239 }
240 final TransitionDataType transition = new TransitionDataType();
241 m_transitions.add( transition );
242 return transition;
243 }
244
245 /**
246 * Add an trigger to the state.
247 * @return the trigger datatype
248 */
249 public TriggerDataType createTrigger()
250 {
251 if( null != m_uri )
252 {
253 final String error =
254 "Triggers may not be used in conjuction with a uri import.";
255 throw new BuildException( error, m_task.getLocation() );
256 }
257 if( null != m_classname )
258 {
259 final String error =
260 "Triggers may not be used in conjuction with the class attribute.";
261 throw new BuildException( error, m_task.getLocation() );
262 }
263 final TriggerDataType trigger = new TriggerDataType();
264 m_triggers.add( trigger );
265 return trigger;
266 }
267
268 State getState()
269 {
270 if( null != m_uri )
271 {
272 m_task.log( "importing state graph: " + m_uri, Project.MSG_VERBOSE );
273 try
274 {
275 return STATE_DECODER.loadState( m_uri );
276 }
277 catch( Exception e )
278 {
279 final String error =
280 "Unable to load an external state graph"
281 + "\nURI: "
282 + m_uri;
283 throw new BuildException( error, e );
284 }
285 }
286 else if( null != m_classname )
287 {
288 m_task.log( "loading state from resource: " + m_classname, Project.MSG_VERBOSE );
289 try
290 {
291 ClassLoader classloader = createClassLoader();
292 Class clazz = classloader.loadClass( m_classname );
293 return loadStateFromResource( clazz );
294 }
295 catch( Exception e )
296 {
297 final String error =
298 "Unable to load an embedded state xgraph from the resource: "
299 + m_classname + ".xgraph";
300 throw new BuildException( error, e, m_task.getLocation() );
301 }
302 }
303 else
304 {
305 m_task.log( "creating embedded state graph", Project.MSG_VERBOSE );
306 String name = getStateName();
307 Trigger[] triggers = getTriggers();
308 Operation[] operations = getOperations();
309 Interface[] interfaces = getInterfaces();
310 Transition[] transitions = getTransitions();
311 State[] states = getStates();
312 return new DefaultState( name, triggers, transitions, interfaces, operations, states, m_terminal );
313 }
314 }
315
316 String getStateName()
317 {
318 if( m_root )
319 {
320 return "";
321 }
322 else
323 {
324 return m_name;
325 }
326 }
327
328 Trigger[] getTriggers()
329 {
330 TriggerDataType[] types = (TriggerDataType[]) m_triggers.toArray( new TriggerDataType[0] );
331 Trigger[] values = new Trigger[ types.length ];
332 for( int i=0; i<types.length; i++ )
333 {
334 TriggerDataType data = types[i];
335 values[i] = data.getTrigger();
336 }
337 return values;
338 }
339
340 Operation[] getOperations()
341 {
342 OperationDataType[] types = (OperationDataType[]) m_operations.toArray( new OperationDataType[0] );
343 Operation[] values = new Operation[ types.length ];
344 for( int i=0; i<types.length; i++ )
345 {
346 OperationDataType data = types[i];
347 values[i] = data.getOperation();
348 }
349 return values;
350 }
351
352 Interface[] getInterfaces()
353 {
354 InterfaceDataType[] types = (InterfaceDataType[]) m_interfaces.toArray( new InterfaceDataType[0] );
355 Interface[] values = new Interface[ types.length ];
356 for( int i=0; i<types.length; i++ )
357 {
358 InterfaceDataType data = types[i];
359 values[i] = data.getInterface();
360 }
361 return values;
362 }
363
364 Transition[] getTransitions()
365 {
366 TransitionDataType[] types = (TransitionDataType[]) m_transitions.toArray( new TransitionDataType[0] );
367 Transition[] values = new Transition[ types.length ];
368 for( int i=0; i<types.length; i++ )
369 {
370 TransitionDataType data = types[i];
371 values[i] = data.getTransition();
372 }
373 return values;
374 }
375
376 State[] getStates()
377 {
378 StateDataType[] types = (StateDataType[]) m_states.toArray( new StateDataType[0] );
379 State[] values = new State[ types.length ];
380 for( int i=0; i<types.length; i++ )
381 {
382 StateDataType data = types[i];
383 values[i] = data.getState();
384 }
385 return values;
386 }
387
388 /**
389 * Return the runtime classloader.
390 * @return the classloader
391 */
392 private ClassLoader createClassLoader()
393 {
394 Project project = m_task.getProject();
395 Path path = m_task.getContext().getPath( Scope.RUNTIME );
396 File classes = m_task.getContext().getTargetClassesMainDirectory();
397 path.createPathElement().setLocation( classes );
398 ClassLoader parentClassLoader = getClass().getClassLoader();
399 return new AntClassLoader( parentClassLoader, project, path, true );
400 }
401
402 private State loadStateFromResource( Class subject )
403 {
404 String resource = subject.getName().replace( '.', '/' ) + ".xgraph";
405 try
406 {
407 URL url = subject.getClassLoader().getResource( resource );
408 if( null == url )
409 {
410 final String error =
411 "The requested state graph resource [" + resource + "] does not exist.";
412 throw new BuildException( error, m_task.getLocation() );
413 }
414 else
415 {
416 URI uri = new URI( url.toString() );
417 return STATE_DECODER.loadState( uri );
418 }
419 }
420 catch( Throwable e )
421 {
422 final String error =
423 "Internal error while attempting to load component state graph resource ["
424 + resource
425 + "].";
426 throw new BuildException( error, e );
427 }
428 }
429 }